package aip.assignment2.persistence;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import javax.persistence.RollbackException;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;

/**
 * This class implements a DAO using JPA.
 */
public abstract class Dao<EntityType extends Serializable>
{
	// <editor-fold desc="Fields" defaultstate="collapsed">
	@PersistenceContext(unitName = "aip_assignment2", type = PersistenceContextType.TRANSACTION)
	protected EntityManager m_EntityManager = null;
	private Class<EntityType> m_EntityClass = null;
	private Map<String, Query> m_QueryCache = new HashMap<String, Query>();
	// </editor-fold>
	// <editor-fold desc="Constructor & Destructor" defaultstate="collapsed">
	protected Dao(Class<EntityType> entityClass)
	{
		m_EntityClass = entityClass;
	}
	// </editor-fold>
	// <editor-fold desc="Methods - Helper" defaultstate="collapsed">
	private boolean fillQueryParameters(Query query, SqlParameter... parameters)
	{
		for (int i = 0; i < parameters.length; i++)
		{
			String parameterName = parameters[i].getName();
			Object parameterValue = parameters[i].getValue();
			if (parameterName == null || parameterValue == null)
			{
				return false;
			}
			Class<?> parameterType = parameterValue.getClass();
			if (parameterType == Date.class)
			{
				query.setParameter(parameterName, (Date) parameterValue, TemporalType.TIMESTAMP);
			}
			else if (parameterType == Calendar.class)
			{
				query.setParameter(parameterName, (Date) parameterValue, TemporalType.TIMESTAMP);
			}
			else
			{
				query.setParameter(parameterName, parameterValue);
			}
		}
		return true;
	}
	private Query getQuery(String sql, SqlParameter... parameters)
	{
		TypedQuery<EntityType> result = m_EntityManager.createQuery(sql, m_EntityClass);
		if (!fillQueryParameters(result, parameters))
		{
			return null;
		}
		return result;
	}
	// </editor-fold>
	// <editor-fold desc="Methods - API">
	/**
	 * This method selects all entities.
	 * @return the result list of entities
	 */
	public List<EntityType> select()
	{
		return select(-1, -1);
	}
	/**
	 * This method selects entities within a specific range.
	 * @param firstIndex the index of the first record (IF negative, the results selection is disabled)
	 * @param maxCount the maximum count of records to select (IF negative, the results selection is disabled)
	 * @return the result list of entities
	 */
	public List<EntityType> select(int firstIndex, int maxCount)
	{
		try
		{
			CriteriaQuery<EntityType> criteriaQuery = m_EntityManager.getCriteriaBuilder().createQuery(m_EntityClass);
			criteriaQuery.select(criteriaQuery.from(m_EntityClass));
			TypedQuery<EntityType> query = m_EntityManager.createQuery(criteriaQuery);
			if (firstIndex >= 0 && maxCount > 0)
			{
				query.setFirstResult(firstIndex);
				query.setMaxResults(maxCount);
			}
			return query.getResultList();
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	/**
	 * This method selects entities by given a SQL statement.
	 * @param sql the SQL statement string
	 * @param parameters the SQL statement parameters
	 * @return the result list of entities
	 */
	public List<EntityType> select(String sql, SqlParameter... parameters)
	{
		return select(-1, -1, sql, parameters);
	}
	/**
	 * This method selects entities by given a SQL statement and the results are selected within a specific range.
	 * @param firstIndex the index of the first record (IF negative, the results selection is disabled)
	 * @param maxCount the maximum count of records to select (IF negative, the results selection is disabled)
	 * @param sql the SQL statement string
	 * @param parameters the SQL statement parameters
	 * @return the result list of entities
	 */
	public List<EntityType> select(int firstIndex, int maxCount, String sql, SqlParameter... parameters)
	{
		if (sql == null)
		{
			throw new IllegalArgumentException();
		}
		sql = sql.trim();
		if (!sql.endsWith(";"))
		{
			sql += ";";
		}
		TypedQuery<EntityType> query = (TypedQuery<EntityType>) getQuery(sql, parameters);
		try
		{
			if (query == null)
			{
				throw new IllegalArgumentException();
			}
			if (firstIndex >= 0 && maxCount > 0)
			{
				query.setFirstResult(firstIndex);
				query.setMaxResults(maxCount);
			}
			return query.getResultList();
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	/**
	 * This method selects entities by given a SQL statement.
	 * @param sql the SQL statement string
	 * @param parameters the SQL statement parameters
	 * @return the result list of entities
	 */
	public <ResultType> ResultType selectSingleResult(String sql, SqlParameter... parameters)
	{
		if (sql == null)
		{
			throw new IllegalArgumentException();
		}
		sql = sql.trim();
		if (!sql.endsWith(";"))
		{
			sql += ";";
		}
		Query query = getQuery(sql, parameters);
		try
		{
			if (query == null)
			{
				throw new IllegalArgumentException();
			}
			return (ResultType) query.getSingleResult();
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	/**
	 * This method selects an entity by its ID.
	 * @param id the entity ID
	 * @return the result entity
	 */
	public EntityType selectById(int id)
	{
		return m_EntityManager.find(m_EntityClass, id);
	}
	/**
	 * This method selects entities by a named query.
	 * @param queryName the name of the query
	 * @param parameters the parameters for the query
	 * @return the result list of entities
	 */
	public List<EntityType> selectByNamedQuery(String queryName, SqlParameter... parameters)
	{
		return selectByNamedQuery(-1, -1, queryName, parameters);
	}
	/**
	 * This method selects entities by a named query.
	 * @param firstIndex the index of the first record (IF negative, the results selection is disabled)
	 * @param maxCount the maximum count of records to select (IF negative, the results selection is disabled)
	 * @param queryName the name of the query
	 * @param parameters the parameters for the query
	 * @return the result list of entities
	 */
	public List<EntityType> selectByNamedQuery(int firstIndex, int maxCount, String queryName, SqlParameter... parameters)
	{
		if (queryName == null)
		{
			throw new IllegalArgumentException();
		}
		TypedQuery<EntityType> query = m_EntityManager.createNamedQuery(queryName, m_EntityClass);
		if (query == null || !fillQueryParameters(query, parameters))
		{
			throw new IllegalArgumentException();
		}
		try
		{
			if (firstIndex >= 0 && maxCount > 0)
			{
				query.setFirstResult(firstIndex);
				query.setMaxResults(maxCount);
			}
			return query.getResultList();
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	/**
	 * This method selects a single result by a named query.
	 * @param queryName the name of the query
	 * @param parameters the parameters for the query
	 * @return the result list of entities
	 */
	public <ResultType> ResultType selectSingleResultByNamedQuery(String queryName, SqlParameter... parameters)
	{
		if (queryName == null)
		{
			throw new IllegalArgumentException();
		}
		Query query = m_EntityManager.createNamedQuery(queryName);
		if (query == null || !fillQueryParameters(query, parameters))
		{
			throw new IllegalArgumentException();
		}
		try
		{
			return (ResultType) query.getSingleResult();
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	/**
	 * This method reloads an existing entity from the persistence, thus the data in the entity will be changed.
	 * @param entity the entity to be reloaded
	 * @return whether it is successful
	 * This exception is thrown when the current transaction is rolled back by the EJB container and the transaction is provided by the method itself rather than the caller.
	 */
	public void reload(EntityType entity)
	{
		m_EntityManager.refresh(entity);
	}
	/**
	 * This method inserts a new entity.
	 * @param entity the new entity to be inserted
	 * @return whether it is successful
	 * This exception is thrown when the current transaction is rolled back by the EJB container and the transaction is provided by the method itself rather than the caller.
	 */
	public void insert(EntityType entity)
	{
		m_EntityManager.persist(entity);
	}
	/**
	 * This method updates an existing entity to the persistence, thus the data in the persistence will be changed.
	 * @param entity the entity to be updated
	 * @return whether it is successful
	 * @throws RollbackException
	 * This exception is thrown when the current transaction is rolled back by the EJB container and the transaction is provided by the method itself rather than the caller.
	 */
	public void update(EntityType entity)
	{
		m_EntityManager.merge(entity);
	}
	/**
	 * This method deletes an entity.
	 * @param entity the entity to be deleted
	 * @return whether it is successful
	 * @throws RollbackException
	 * This exception is thrown when the current transaction is rolled back by the EJB container and the transaction is provided by the method itself rather than the caller.
	 */
	public void delete(EntityType entity)
	{
		m_EntityManager.remove(entity);
	}
	/**
	 * This method executes a SQL insert/update/delete statement.
	 * @param sql the SQL statement string
	 * @param parameters the SQL statement parameters
	 * @return the count of records affected
	 */
	public int execute(String sql, SqlParameter... parameters)
	{
		if (sql == null)
		{
			throw new IllegalArgumentException();
		}
		Query query = getQuery(sql, parameters);
		if (query == null)
		{
			throw new IllegalArgumentException();
		}
		return query.executeUpdate();
	}
	/**
	 * This method executes a SQL insert/update/delete named query.
	 * @param queryName the query name
	 * @param parameters the SQL statement parameters
	 * @return the count of records affected
	 */
	public int executeByNamedQuery(String queryName, SqlParameter... parameters)
	{
		if (queryName == null)
		{
			throw new IllegalArgumentException();
		}
		Query query = m_EntityManager.createNamedQuery(queryName);
		if (query == null || !fillQueryParameters(query, parameters))
		{
			throw new IllegalArgumentException();
		}
		return query.executeUpdate();
	}
	// </editor-fold>
}
